/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
 
package com.cburch.logisim.std.io;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;

import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Attributes;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.UItools.GraphicsUtil;
import com.cburch.logisim.util.IntegerFactory;
import com.cburch.logisim.util.Strings;

public class Tty extends InstanceFactory {
    private static final int CLR = 0;
    private static final int CK = 1;
    private static final int WE = 2;
    private static final int IN = 3;
    
    private static final int BORDER = 5;
    private static final int ROW_HEIGHT = 15;
    private static final int COL_WIDTH = 7; 
    private static final Color BG_COLOR = new Color(0, 0, 0, 64);
    
    private static final Font DEFAULT_FONT = new Font("monospaced", Font.PLAIN, 12);

    private static final Attribute ATTR_COLUMNS
        = Attributes.forIntegerRange("cols",
            Strings.getter("ttyColsAttr", Strings.stdSource), 1, 120);
    private static final Attribute ATTR_ROWS
        = Attributes.forIntegerRange("rows",
            Strings.getter("ttyRowsAttr", Strings.stdSource), 1, 48);

    public Tty() {
        super("TTY", Strings.getter("ttyComponent", Strings.stdSource));
        setAttributes(new Attribute[] {
                ATTR_ROWS, ATTR_COLUMNS, StdAttr.EDGE_TRIGGER
            }, new Object[] {
                IntegerFactory.create(8), IntegerFactory.create(32), StdAttr.TRIG_RISING
            });
        setIconName("tty.gif");
        
        Port[] ps = new Port[4];
        ps[CLR] = new Port(20,  10, Port.INPUT, 1);
        ps[CK]  = new Port( 0,   0, Port.INPUT, 1);
        ps[WE]  = new Port(10,  10, Port.INPUT, 1);
        ps[IN]  = new Port( 0, -10, Port.INPUT, 7);
        ps[CLR].setToolTip(Strings.getter("ttyClearTip", Strings.stdSource));
        ps[CK].setToolTip(Strings.getter("ttyClockTip", Strings.stdSource));
        ps[WE].setToolTip(Strings.getter("ttyEnableTip", Strings.stdSource));
        ps[IN].setToolTip(Strings.getter("ttyInputTip", Strings.stdSource));
        setPorts(ps);
    }
    
    public Bounds getOffsetBounds(AttributeSet attrs) {
        int rows = getRowCount(attrs.getValue(ATTR_ROWS));
        int cols = getColumnCount(attrs.getValue(ATTR_COLUMNS));
        int width = 2 * BORDER + cols * COL_WIDTH;
        int height = 2 * BORDER + rows * ROW_HEIGHT;
        if(width < 30) width = 30;
        if(height < 30) height = 30;
        return Bounds.create(0, 10 - height, width, height);
    }

    protected void configureNewInstance(Instance instance) {
        instance.addAttributeListener();
    }

    protected void instanceAttributeChanged(Instance instance, Attribute attr) {
        if(attr == ATTR_ROWS || attr == ATTR_COLUMNS) {
            instance.recomputeBounds();
        }
    }

    public void propagate(InstanceState circState) {
        Object trigger = circState.getAttributeValue(StdAttr.EDGE_TRIGGER);
        TtyState state = getTtyState(circState);
        Value clear = circState.getPort(CLR);
        Value clock = circState.getPort(CK);
        Value enable = circState.getPort(WE);
        Value in = circState.getPort(IN);
        
        synchronized(state) {
            Value lastClock = state.setLastClock(clock);
            if(clear == Value.TRUE) {
                state.clear();
            } else if(enable != Value.FALSE) {
                boolean go;
                if(trigger == StdAttr.TRIG_FALLING) {
                    go = lastClock == Value.TRUE && clock == Value.FALSE;
                } else {
                    go = lastClock == Value.FALSE && clock == Value.TRUE;
                }
                if(go) state.add(in.isFullyDefined() ? (char) in.toIntValue() : '?');
            }
        }
    }

    public void paintGhost(InstancePainter painter) {
        Graphics g = painter.getGraphics();
        GraphicsUtil.switchToWidth(g, 2);
        Bounds bds = painter.getBounds();
        g.drawRoundRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight(),
                10, 10);
    }

    public void paintInstance(InstancePainter painter) {
        boolean showState = painter.getShowState();
        Graphics g = painter.getGraphics();
        Bounds bds = painter.getBounds();
        painter.drawClock(CK, Direction.EAST);
        if(painter.shouldDrawColor()) {
            g.setColor(BG_COLOR);
            g.fillRoundRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight(),
                    10, 10);
        }
        GraphicsUtil.switchToWidth(g, 2);
        g.setColor(Color.BLACK);
        g.drawRoundRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight(),
                2 * BORDER, 2 * BORDER);
        GraphicsUtil.switchToWidth(g, 1);
        painter.drawPort(CLR);
        painter.drawPort(WE);
        painter.drawPort(IN);
        
        int rows = getRowCount(painter.getAttributeValue(ATTR_ROWS));
        int cols = getColumnCount(painter.getAttributeValue(ATTR_COLUMNS));

        if(showState) {
            String[] rowData = new String[rows];
            int curRow;
            int curCol;
            TtyState state = getTtyState(painter);
            synchronized(state) {
                for(int i = 0; i < rows; i++) {
                    rowData[i] = state.getRowString(i);
                }
                curRow = state.getCursorRow();
                curCol = state.getCursorColumn();
            }

            g.setFont(DEFAULT_FONT);
            FontMetrics fm = g.getFontMetrics();
            int x = bds.getX() + BORDER;
            int y = bds.getY() + BORDER + (ROW_HEIGHT + fm.getAscent()) / 2;
            for(int i = 0; i < rows; i++) {
                g.drawString(rowData[i], x, y);
                if(i == curRow) {
                    int x0 = x + fm.stringWidth(rowData[i].substring(0, curCol));
                    g.drawLine(x0, y - fm.getAscent(), x0, y);
                }
                y += ROW_HEIGHT;
            }
        } else {
            String str = Strings.get("ttyDesc", "" + rows, "" + cols, Strings.stdSource);
            FontMetrics fm = g.getFontMetrics();
            int strWidth = fm.stringWidth(str);
            if(strWidth + BORDER > bds.getWidth()) {
                str = Strings.get("ttyDescShort", Strings.stdSource);
                strWidth = fm.stringWidth(str);
            }
            int x = bds.getX() + (bds.getWidth() - strWidth) / 2;
            int y = bds.getY() + (bds.getHeight() + fm.getAscent()) / 2;
            g.drawString(str, x, y);
        }
    }

    private TtyState getTtyState(InstanceState state) {
        int rows = getRowCount(state.getAttributeValue(ATTR_ROWS));
        int cols = getColumnCount(state.getAttributeValue(ATTR_COLUMNS));
        TtyState ret = (TtyState) state.getData();
        if(ret == null) {
            ret = new TtyState(rows, cols);
            state.setData(ret);
        } else {
            ret.updateSize(rows, cols);
        }
        return ret;
    }
    
    private static int getRowCount(Object val) {
        if(val instanceof Integer) return ((Integer) val).intValue();
        else return 4;
    }
    
    private static int getColumnCount(Object val) {
        if(val instanceof Integer) return ((Integer) val).intValue();
        else return 16;
    }
}
